Functional Programming


Scala grammar basic


In [1]:
/* 
def functionName(parameterName: type, name: type, ...): return_type = {
  statement
  ...
}
*/
def sum(a: Int, b:Int): Int = { // def 키워드로 함수 정의 '='뒤 중괄호 안에 함수 본문
  val c = a + b // val 키워드로 상수 정의
  println(c)
  c  // 마지막 라인의 값이 함수의 리턴값, 파이썬과 달리 return 키워드 사용안함
}


Out[1]:
defined function sum

In [2]:
sum(3, 4)


7
Out[2]:
res1: Int = 7

In [3]:
// 주석
/* 주석2 */
/** 문서화 주석 */
class MyClass { // class 키워드는 클래스를 선언. 중괄호 `{`,`}` 사이에 클래스 본문 
  
}
object MyModule { // object 키워드는 singletone객체(인스턴스가 하나인 객체) 생성 
  def abs(n: Int): Int =  // 함수 본문의 statement가 하나면 중괄호 생략가능
    if ( n < 0 ) {  // if 키워드 뒤 소괄호안에 조건식 이후 중괄호 안에 조건문
        -n
    }
    else n // 함수와 마찬가지로 본문 statement가 한개면 중괄호 생략가능
  
  private def formatAbs(x: Int): String = { // private 메서드는 해당 객체에서만 호출
    val msg: String = "The absolute value of %d is %d"
    msg.format(x, abs(x))
  }
  
  def main(args: Array[String]): Unit = // Unit은 Java의 void, Python의 None과 비슷
    println(-42)
}


Out[3]:
defined class MyClass
defined object MyModule

Side effect, Pure, Referential transparency

Side effect

side effect는 함수의 입력값에 따른 결과값(return 값) 이외의 행동들에 대한 것들이다. 예를 들어 변수의 값을 수정, 화면에 문자를 출력 등의 작업이 side effect에 해당한다.

Pure

side effect가 없는 것을 pure(순수)하다고 한다.

Referential transparency

함수에 정의된 어떤 expression(표현식)을 함수 본문에서 그 표현식을 모두 치환하여도 정상적으로 작동하는 것을 말한다. 예를들어, val x = “bug bug debug”라는 expression이 있고 코드의 다른 부분에서 x를 사용하는 부분이 있다고 가정했을 때, 그 x를 등호의 오른쪽 표현식인 “bug bug debug”라는 스트링으로 치환 했을때, 코드의 동작이나 의미에 아무런 영향이 없으면 ‘참조에 투명하다’ 라고 한다.


In [1]:
// 참조에 투명한 경우
val x = "Hello, World"
val r1 = x.reverse
val r2 = x.reverse


Out[1]:
x: String = "Hello, World"
r1: String = "dlroW ,olleH"
r2: String = "dlroW ,olleH"

In [5]:
val r1_ = "Hello, World".reverse
val r2_ = "Hello, World".reverse


Out[5]:
r1_: String = "dlroW ,olleH"
r2_: String = "dlroW ,olleH"

In [6]:
// 참조에 투명하지 않은 경우
val l = new StringBuilder("Hello")
val m = l.append(", World")
val rr1 = m.toString
val rr2 = m.toString


Out[6]:
l: StringBuilder = StringBuilder('H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd')
m: StringBuilder = StringBuilder('H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd')
rr1: String = "Hello, World"
rr2: String = "Hello, World"

In [7]:
val b = new StringBuilder("Hello")
val rr1_ = b.append(", World").toString
val rr2_ = b.append(", World").toString


Out[7]:
b: StringBuilder = StringBuilder(
  'H',
  'e',
  'l',
  'l',
  'o',
  ',',
  ' ',
  'W',
  'o',
  'r',
  'l',
...
rr1_: String = "Hello, World"
rr2_: String = "Hello, World, World"

Tail recursion

재귀함수 호출부분에 함수 호출 외의 연산식이 없는 재귀함수.

재귀함수의 고질적인 문제인 stackoverflow를 해결할 수 있음.


In [8]:
// 일반적인 재귀함수
def sumToN(n: Int): Int = {
//   @annotation.tailrec
  def go(n: Int): Int = 
    if (n>0) n + go(n-1)
    else 0
  go(n)
}

sumToN(4)


Out[8]:
defined function sumToN
res7_1: Int = 10

In [9]:
// 꼬리재귀함수
def factorial(n: Int): Int = {
  // tailrec
  // 재귀함수 서명위에 아래의 annotation(주해)를 추가하면 
  // 컴파일러가 꼬리재귀인지 검사해주고 아니면 에러를 냄
  @annotation.tailrec  
  def go(n: Int, acc: Int): Int = 
    if(n<=0) acc
    else go(n-1, acc*n)
  go(n, 1)
}
factorial(4)


Out[9]:
defined function factorial
res8_1: Int = 24

연습문제 2.1

n번째 피보나치 수를 돌려주는 재귀 함수를 작성하라. 처음 두 피보나치 수는 0과 1이다. n번째 피보나치 수는 항상 이전 두 수의 합이다. 즉, 피보나치 수열은 0, 1, 1, 2, 3, 5로 시작한다. 반드시 지역 꼬리재귀 함수를 사용해서 작성할 것.

def fib(n: Int): Int
  1. 0 + 1 = 1
  2. 1 + 1 = 2
  3. 1 + 2 = 3
  4. 2 + 3 = 5
  5. 3 + 5 = 8

In [1]:
def fib(n: Int): Int = {
  @annotation.tailrec
  def go(n: Int, acc: Int, pre: Int): Int = {
    if( n <= 1 ) acc
    else go(n-1, acc+pre, acc)
  }
  go(n, 1, 0)
}


Out[1]:
defined function fib

In [2]:
fib(10)


Out[2]:
res1: Int = 55

연습문제 2.2

Array[A]가 주어진 비교 함수에 의거해서 정렬되어 있는지 점검하는 isSorted함수를 구현하라. 서명은 다음과 같다.

def isSorted[A](as: Array[A], ordered: (A,A) => Boolean): Boolean

In [ ]:
def isSorted[A](as: Array[A], ordered: (A,A) => Boolean): Boolean = {
    def loop(n: Int): Boolean =
        if (n == as.length -1) true
        else if (ordered(as(n), as(n+1))) loop(n+1)
        else false
    loop(0)
}